package com.cnebula.openapi.auth.provider;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

import com.cnebula.openapi.auth.AuthOptions;
import com.cnebula.openapi.auth.exception.OAuthResponseTypeUnsupported;
import com.cnebula.openapi.auth.exception.SignException;
import com.cnebula.openapi.auth.exception.SignIllegalException;
import com.cnebula.openapi.auth.service.ClientAppService;
import com.cnebula.openapi.auth.service.OAuthService;
import com.cnebula.openapi.auth.service.SignService;
import com.cnebula.openapi.auth.util.RestUtil;
import com.cnebula.openapi.auth.vo.AccessToken;
import com.cnebula.openapi.auth.vo.AuthCode;
import com.cnebula.openapi.auth.vo.ClientApp;
import com.cnebula.openapi.auth.vo.User;

import io.vertx.core.Future;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.web.RoutingContext;

public class LoginProvider {

	SignService signService;
//	AuthCodeService authCodeService;
	OAuthService oauthService;
	ClientAppService clientAppService;
	private final AuthOptions options;
	private final String lpURI;
	private final String lspLoginURI;

	public static Logger log = LoggerFactory.getLogger(LoginProvider.class);

	public LoginProvider(SignService signService, OAuthService oauthService,
			ClientAppService clientAppService, AuthOptions options) {
		this.signService = signService;
		this.oauthService = oauthService;
		this.clientAppService = clientAppService;
		this.options = options;

		lpURI = "http://" + options.getHost() + ":" + options.getPort() + "/loginp/code";
		
		lspLoginURI = "http://" + options.getLspHost() + ":" + options.getLspPort() + "/loginp";
	}

	public void loginProxy4LSP(RoutingContext context) {

		HttpServerRequest request = context.request();

		String clientKey = request.getParam("client_id");
		String userType = request.getParam("user_type");
		String cbURL = request.getParam("redirect_uri");
		String vf = request.getParam("view_format");
		String tenantId = request.getParam("tenant_id");
		String sign = request.getParam("sign");
		String responseType = request.getParam("response_type");
		if (userType == null || userType.isEmpty()) {
			userType = "staff";
		}
		if (vf == null || vf.isEmpty()) {
			vf = "pc";
		}

		Map<String, String> requiredParams = new HashMap<String, String>();
		requiredParams.put("client_id", clientKey);
		requiredParams.put("redirect_uri", cbURL);
		requiredParams.put("tenant_id", tenantId);
		requiredParams.put("sign", sign);
		requiredParams.put("response_type", responseType);
		if(!RestUtil.checkRequired(requiredParams, context)) {
			return;
		}
		if(!responseType.equals(OAuthService.RESPONSE_TYPE)) {
			RestUtil.responseError(new OAuthResponseTypeUnsupported(), context);
			return;
		}
		
		StringBuilder lspLogin = new StringBuilder(lspLoginURI);
		try {
			String lpSign = this.createLpSign(sign);
			
			lspLogin.append("?");
			lspLogin.append("client_id").append("=").append(clientKey);
			lspLogin.append("&");
			lspLogin.append("tenant_id").append("=").append(tenantId);
			lspLogin.append("&");
			lspLogin.append("response_type").append("=").append(responseType);
			lspLogin.append("&");
			lspLogin.append("user_type").append("=").append(userType);
			lspLogin.append("&");
			lspLogin.append("view_format").append("=").append(vf);
			lspLogin.append("&");
			lspLogin.append("sign").append("=").append(sign);
			lspLogin.append("&");
			lspLogin.append("lp_sign").append("=").append(lpSign);
			lspLogin.append("&");
			lspLogin.append("lp_uri").append("=").append(URLEncoder.encode(lpURI, "UTF-8"));
			lspLogin.append("&");
			lspLogin.append("redirect_uri").append("=").append(URLEncoder.encode(cbURL, "UTF-8"));
		} catch (UnsupportedEncodingException | SignException e) {
			RestUtil.responseError(e, context);
			return;
		}
		//验证合法性
		clientAppService.checkKey(clientKey, tenantId, request.remoteAddress()).setHandler(h -> {
			if(h.succeeded()) {
				ClientApp clientApp = h.result();
				//签名验证
				try {
					checkSign(clientKey, tenantId, cbURL, clientApp.clientSecret, sign, responseType);
					RestUtil.redirect(lspLogin.toString(), context);
				}catch(Exception e) {
					RestUtil.responseError(e, context);
				}
			}else{
				Throwable e = h.cause();
				RestUtil.responseError(e, context);
			}
		});
	}

	public void loginProxy4LSPCallback(RoutingContext context) {
		HttpServerRequest request = context.request();

//		JsonObject body = context.getBodyAsJson();

		String clientKey = request.getParam("client_id");
		String cbURL = request.getParam("redirect_uri");
		String tenantId = request.getParam("tenant_id");
		String sign = request.getParam("sign");
		String okapiToken = request.getParam("X-Okapi-Token");
		String expire = request.getParam("expire");
		String userId = request.getParam("userid");
		String userName = request.getParam("username");
		String lpSign = request.getParam("lp_sign");
//		String responseType = request.getParam("response_type");
		
		log.debug("lsp return token:"+ okapiToken);

		Map<String, String> requiredParams = new HashMap<String, String>();
		requiredParams.put("client_id", clientKey);
		requiredParams.put("redirect_uri", cbURL);
		requiredParams.put("tenant_id", tenantId);
		requiredParams.put("sign", sign);
		requiredParams.put("X-Okapi-Token", okapiToken);
		requiredParams.put("expire", expire);
		requiredParams.put("userid", userId);
		requiredParams.put("username", userName);
//		requiredParams.put("response_type", responseType);
		requiredParams.put("lp_sign", lpSign);
		if(!RestUtil.checkRequired(requiredParams, context)) {
			return;
		}
		Future<Void> checkFuture = Future.future();
		//验证合法性
		clientAppService.checkKey(clientKey, tenantId, request.remoteAddress()).setHandler(h -> {
			if(h.succeeded()) {
				ClientApp clientApp = h.result();
				try {
					//签名验证
					checkSign(clientKey, tenantId, cbURL, clientApp.clientSecret, sign,OAuthService.RESPONSE_TYPE);
					checkLpSign(sign,lpSign);
					
					checkFuture.complete();
				}catch(Exception e) {
					checkFuture.fail(e);
				}
			}else{
				checkFuture.fail(h.cause());
			}
		});
		
		User user = new User();
		user.userId = userId;
		user.userName = userName;
		
		AccessToken accessToken = new AccessToken();
		accessToken.token = okapiToken;
		accessToken.expire = Long.parseLong(expire);
		accessToken.userId = userId;
		accessToken.userName = userName;

		checkFuture.compose(checkRst -> oauthService.createCode(clientKey, tenantId, user, accessToken))
				.setHandler(h -> {
					if (h.succeeded()) {
						AuthCode authCode = h.result();
						// 携带授权码 跳转到 redirect_uri
						StringBuilder appCallback = new StringBuilder(cbURL);
						if (cbURL.indexOf("?") > 0) {
							appCallback.append("&");
						} else {
							appCallback.append("?");
						}
						appCallback.append("code").append("=").append(authCode.code);
						RestUtil.redirect(appCallback.toString(), context);
					} else {
						Throwable e = h.cause();
						RestUtil.responseError(e, context);
					}
				});
	}
	
	private String createLpSign(String clientSign) throws SignException{
		Map<String, String> params = new HashMap<String, String>();
		params.put("sign", clientSign);
		String lpSign = signService.sign(params, options.getLpSecret());
		return lpSign;
	}
	
	private void checkLpSign(String sign, String lpSign) throws SignException, SignIllegalException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("sign", sign);
		signService.check(params, options.getLpSecret(), lpSign);
	}
	
	private void checkSign(String clientKey, String tenantId, String cbURL, String clientSecret, String sign, String responseType) throws SignException, SignIllegalException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("client_id", clientKey);
		params.put("redirect_uri", cbURL);
		params.put("tenant_id", tenantId);
		params.put("response_type", responseType);
		signService.check(params, clientSecret, sign);
	}
}
